package com.twoscreen;

import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Text;
import com.google.gson.JsonArray;
import com.google.gson.JsonPrimitive;
import com.twoscreen.test.Test;
import com.twoscreen.test.TestMethod;

@PersistenceCapable
public class Event implements Comparable<Event>{
	@NotPersistent
	private static PersistenceManager pm = PMF.get().getPersistenceManager();
	
	@NotPersistent
	private static final Logger log = Logger.getLogger("com.twoscreen.Event.java");
	
	@NotPersistent
	private static final String videoIdPropertyName = "videoId";
	
	@NotPersistent
	private static final String timePropertyName = "time";
	
	@NotPersistent
	private static final String actionPropertyName = "action";
	
	@NotPersistent
	private static final String headerPropertyName = "header";
	
	@NotPersistent
	private static final String bodyPropertyName = "body";
	
	@NotPersistent
	private static final String fullScreenPropertyName = "fullScreen";
	
	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Key key;
	
	@Persistent
	private String videoId;
	
	@Persistent
	private int time;
	
	@Persistent
	private String action;
	
	@Persistent
	private String title;
	
	@Persistent
	private Text body;
	
	@Persistent
	private Boolean fullScreen;
	
	public Event(String videoId, String viewerId, int time, String action, String title, Text body, Boolean fullScreen,
			String vidTitle, String vidImg) {
		Key key = KeyFactory.createKey(Event.class.getSimpleName(), videoId+time);
		this.key = key;
		this.videoId = videoId;
		this.time = time;
		this.action = action;
		this.title = title;
		this.body = body;
		this.fullScreen = fullScreen;
		Key fkey = KeyFactory.createKey(Video.class.getSimpleName(), videoId);
		Video vid = null;
		try {
			vid = pm.getObjectById(Video.class, fkey);
		} catch (NullPointerException e) {
			log.info("Video not found: " + videoId + " error: " + e);
		} catch (JDOObjectNotFoundException e) {
			log.info("Video not found: " + videoId + " error: " + e);
		}
		if (vid == null) {
			log.info("Video does not exist in library - adding");
			new Video(videoId, vidTitle, vidImg, viewerId);
		} else {
			log.info("Video exists in library");
		}
		pm.makePersistent(this);
		log.info("Event added: id: " + videoId + time +
				" viewerId: " + viewerId +
				" videoId: " + videoId +
				" time: " + time +
				" action: " + action);
	}
	
	public Key getKey() {
		return key;
	}

	public String getVideoId() {
		return videoId;
	}

	public int getTime() {
		return time;
	}

	public String getAction() {
		return action;
	}
	
	public String getTitle() {
		return title;
	}

	public Text getBody() {
		return body;
	}
	
	public Boolean getFullScreen() {
		return fullScreen;
	}

	public static String getVideoidpropertyname() {
		return videoIdPropertyName;
	}

	public static String getTimepropertyname() {
		return timePropertyName;
	}

	public static String getActionpropertyname() {
		return actionPropertyName;
	}
	
	public static String getHeaderpropertyname() {
		return headerPropertyName;
	}

	public static String getBodypropertyname() {
		return bodyPropertyName;
	}
	
	/**
	 * Returns a JSON array of event times for the passed video ID
	 * Method used in play.jsp
	 * @param videoId Video ID to get times for
	 * @return JSON array of times
	 */
	public static JsonArray getTimesJson(String videoId) {
		log.info("Getting times in JSON format for video ID: " + videoId);
		JsonArray jsonTimes = new JsonArray();
		
		Query q = pm.newQuery(Event.class);
		q.setFilter(videoIdPropertyName + " == vidIdParam");
		q.declareParameters("String vidIdParam");
		
		try {
			@SuppressWarnings("unchecked")
			List<Event> results = (List<Event>) q.execute(videoId);
			if (!results.isEmpty()) {
				for (Event result : results) {
					long time = result.getTime();
					JsonPrimitive jsonTime = new JsonPrimitive(time);
					jsonTimes.add(jsonTime);
				}
			}
		} finally {
			q.closeAll();
		}
		
		return jsonTimes;
	}
	
	/**
	 * Returns the body text for a given Event based on the key
	 * Used by EventServlet
	 * @param key Key for the Event
	 * @return String of body Text field
	 */
	protected static String getBodyText(Key key) {
		String body = null;
		try {
			body = pm.getObjectById(Event.class, key).getBody().getValue();
		} catch (NullPointerException e) {
			log.info("Get body text failed " + e);
		} catch (JDOObjectNotFoundException e) {
			log.info("Get body text failed " + e);
		}
		return body;
	}

	/**
	 * Returns a JSON Array of the title and time of events for a given video ID
	 * Used in EventServlet
	 * @param videoId Video ID to create an array for
	 * @return JSON Array as a String
	 */
	public static JSONArray getEventsJson(String videoId) {
		log.info("Getting events in JSON format for video ID: " + videoId);
		JSONArray rtn = new JSONArray();
		List<Event> events = Event.getEvents(videoId);
		for (Event event : events) {
			JSONObject obj = new JSONObject();
			try {
				obj.put("title", event.getTitle());
				obj.put("time", event.getTime());
			} catch (JSONException e) {
				log.warning("JSON Error: " + e.getMessage());
			}
			rtn.put(obj);
		}
		return rtn;
	}
	
	/**
	 * Delete an Event object from the data store
	 * @param key Key for the Event object
	 */
	public static void deleteEvent(Key key) {
		Event d = pm.getObjectById(Event.class, key);
		pm.deletePersistent(d);
	}
	
	/**
	 * Get an Event object from the data store (read only)
	 * @param key Key for the Event object
	 * @return Event object
	 */
	public static Event getEvent(Key key) {
		Event event = pm.getObjectById(Event.class, key);
		return event;
	}
	
	/**
	 * Returns an array of Event objects matching the video ID passed
	 * @param videoId The video ID to get content for
	 * @return A List of Event objects
	 */
	@SuppressWarnings("unchecked")
	public static List<Event> getEvents(String videoId) {
		Query q = pm.newQuery(Event.class);
		q.setFilter(videoIdPropertyName + " == videoIdParam");
		q.declareParameters("String videoIdParam");
		
		try {
			List<Event> results = (List<Event>) q.execute(videoId);
			log.info("Original order");
			for (Event result : results) {
				log.info("time: " + result.getTime());
			}
			Collections.sort(results);
			log.info("Sorted order");
			for (Event result : results) {
				log.info("time: " + result.getTime());
			}
			return results;
		} finally {
			q.closeAll();
		}
		
	}
	
	/**
	 * Enables comparing of Event time
	 */
	@Override
	public int compareTo(Event o) {
		return this.time - o.time;
	}
	
	/**
	 * Runs all the tests on the class
	 * @return Test results JSON
	 */
	public static JSONObject test() {
		Test test = new Test("Event");
		test.addMethod(testConstructor());
		test.addMethod(testGetTimesJson());
		test.addMethod(testGetBodyText());
		test.addMethod(testGetEventsJson());
		test.addMethod(testDeleteEvent());
		return test.getJSONObject();
	}
	
	/**
	 * Tests the constructor method
	 * @return Test results JSON
	 */
	private static JSONObject testConstructor() {
		TestMethod tm = new TestMethod("testConstructor");
		
		//Test base
		String videoId = "abcd";
		String viewerId = "efgh";
		int time = 12;
		String action = "ijkl";
		String title = "mnop";
		Text body = new Text("body text");
		Boolean fullScreen = true;
		String vidTitle = "qrst";
		String vidImg = "uvwx";
		Event test = new Event(videoId, viewerId, time, action, title, body, fullScreen, vidTitle, vidImg);
    	
		//Test 1
		Boolean test1 = false;
    	String testStack1 = "";
    	try {
    		Event inTest = pm.getObjectById(Event.class, videoId+time);
    		test1 = (inTest != null);
    	} catch (Exception e) {
    		testStack1 = e.getMessage();
    	}
		tm.addTest("Entry exists", test1, testStack1);
		
		//Test 2
		Boolean test2 = false;
    	String testStack2 = "";
    	try {
    		Video inTest = Video.getVideo(videoId);
    		test2 = (inTest.getVideoTitle().equals(vidTitle));
    	} catch (Exception e) {
    		testStack2 = e.getMessage();
    	}
		tm.addTest("Video entry exists", test2, testStack2);
		
		//Remove the test object
		pm.deletePersistent(test);
		try {
			Video vid = Video.getVideo(videoId);
			pm.deletePersistent(vid);
		} catch (Exception e) {
			log.warning("No video found for videoID: " + videoId);
		}
		
    	return tm.getJSONObject();
    }
	
	/**
	 * Tests the getTimesJson method
	 * @return Test results JSON
	 */
	private static JSONObject testGetTimesJson() {
		TestMethod tm = new TestMethod("testGetTimesJson");
		
		//Test base
		String videoId = "abcd";
		String viewerId = "efgh";
		int time = 12;
		String action = "ijkl";
		String title = "mnop";
		Text body = new Text("body text");
		Boolean fullScreen = true;
		String vidTitle = "qrst";
		String vidImg = "uvwx";
		Event testItem1 = new Event(videoId, viewerId, time, 
				action, title, body, fullScreen, vidTitle, vidImg);
		JsonArray test = getTimesJson(videoId);
		
		//Test 1
		Boolean test1 = false;
    	String testStack1 = "";
    	try {
    		test1 = (test.size() >= 1);
    		testStack1 += "Number of elements: " + test.size() + "<br>";
    		testStack1 += "Result: " + test + "<br>";
    	} catch (Exception e) {
    		testStack1 += e.getMessage();
    	}
		tm.addTest("Contains a time", test1, testStack1);
		
		//Remove the test object
		pm.deletePersistent(testItem1);
		
    	return tm.getJSONObject();
    }
	
	/**
	 * Tests the getBodyText method
	 * @return Test results JSON
	 */
	private static JSONObject testGetBodyText() {
		TestMethod tm = new TestMethod("testGetBodyText");
		
		//Test base
		String videoId = "abcd";
		String viewerId = "efgh";
		int time = 12;
		String action = "ijkl";
		String title = "mnop";
		Text body = new Text("body text");
		Boolean fullScreen = true;
		String vidTitle = "qrst";
		String vidImg = "uvwx";
		Event testItem1 = new Event(videoId, viewerId, time, 
				action, title, body, fullScreen, vidTitle, vidImg);
		Key key = testItem1.getKey();
		String test = getBodyText(key);
		
		//Test
		Boolean test1 = false;
    	String testStack1 = "";
    	try {
    		test1 = (test.equals("body text"));
    		testStack1 += "Result: " + test + "<br>";
    	} catch (Exception e) {
    		testStack1 += e.getMessage();
    	}
		tm.addTest("Body correct", test1, testStack1);
		
		//Remove the test object
		pm.deletePersistent(testItem1);
		
    	return tm.getJSONObject();
    }
	
	/**
	 * Tests the getEventsJson method
	 * @return Test results JSON
	 */
	private static JSONObject testGetEventsJson() {
		TestMethod tm = new TestMethod("testGetEventsJson");
		
		//Test base
		String videoId = "abcd";
		String viewerId = "efgh";
		int time = 12;
		String action = "ijkl";
		String title = "mnop";
		Text body = new Text("body text");
		Boolean fullScreen = true;
		String vidTitle = "qrst";
		String vidImg = "uvwx";
		Event testItem1 = new Event(videoId, viewerId, time, 
				action, title, body, fullScreen, vidTitle, vidImg);
		JSONArray test = getEventsJson(videoId);
		
		//Test
		Boolean test1 = false;
    	String testStack1 = "";
    	try {
    		test1 = (test.length() == 1);
    		testStack1 += "Number of elements: " + test.length() + "<br>";
    		testStack1 += "Result: " + test + "<br>";
    	} catch (Exception e) {
    		testStack1 += e.getMessage();
    	}
		tm.addTest("Contains correct number of elements", test1, testStack1);
		
		//Remove the test object
		pm.deletePersistent(testItem1);
		
    	return tm.getJSONObject();
    }
	
	/**
	 * Tests the getEventsJson method
	 * @return Test results JSON
	 */
	private static JSONObject testDeleteEvent() {
		TestMethod tm = new TestMethod("testDeleteEvent");
		
		//Test base
		String videoId = "abcd";
		String viewerId = "efgh";
		int time = 12;
		String action = "ijkl";
		String title = "mnop";
		Text body = new Text("body text");
		Boolean fullScreen = true;
		String vidTitle = "qrst";
		String vidImg = "uvwx";
		Event testItem1 = new Event(videoId, viewerId, time, 
				action, title, body, fullScreen, vidTitle, vidImg);
		Key key = testItem1.getKey();
		
		//Test
		Boolean test1 = false;
    	String testStack1 = "";
    	try {
    		Event inTest = pm.getObjectById(Event.class, videoId+time);
    		test1 = (inTest != null);
    	} catch (Exception e) {
    		testStack1 += e.getMessage();
    	}
		tm.addTest("Entry exists", test1, testStack1);
		
		//Test event
		deleteEvent(key);
		
		//Test
		Boolean test2 = false;
    	String testStack2 = "";
    	try {
    		Event inTest = pm.getObjectById(Event.class, videoId+time);
    		test2 = (inTest.getTitle() == null);
    		testStack2 += "Result: " + inTest.getTitle() + "<br>";
    	} catch (Exception e) {
    		testStack2 += e.getMessage();
    	}
		tm.addTest("Entry does not exist", test2, testStack2);
		
    	return tm.getJSONObject();
    }

}
